/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package L3Interpreter

abstract class Tipo
case class Inteiro() extends Tipo
case class Boleano() extends Tipo
case class Unidade() extends Tipo
case class Funcao(t1:Tipo, t2:Tipo) extends Tipo
case class IntRef() extends Tipo
case class TipoRaise() extends Tipo

abstract class Expr
case class N (n:Int) extends Expr
case class B (b:Boolean) extends Expr

case class Sum (e1: Expr, e2: Expr) extends Expr
case class Prod (e1: Expr, e2: Expr) extends Expr
case class Sub (e1: Expr, e2: Expr) extends Expr
case class Div (e1: Expr, e2: Expr) extends Expr

case class GrEq (e1: Expr, e2: Expr) extends Expr
case class LessEq (e1: Expr, e2: Expr) extends Expr
case class Greater (e1: Expr, e2: Expr) extends Expr
case class Less (e1: Expr, e2: Expr) extends Expr
case class Equal (e1: Expr, e2: Expr) extends Expr
case class Dif (e1: Expr, e2: Expr) extends Expr

case class If (e1: Expr, e2: Expr, e3: Expr) extends Expr
case class Asg (l:String, e: Expr) extends Expr
case class Deref (l:String) extends Expr
case class Skip() extends Expr
case class Seq (e1: Expr, e2: Expr) extends Expr
case class W (e1: Expr, e2: Expr) extends Expr
case class Fn (s:String, t: Tipo, e: Expr) extends Expr
case class App (e1: Expr, e2: Expr) extends Expr
case class X (s:String) extends Expr
case class Let (s:String, t: Tipo, e1: Expr, e2: Expr) extends Expr
case class LetRec (s:String, f: Tipo, e1: Expr, e2: Expr) extends Expr
case class Raise (t:Expr, w:Expr) extends Expr



object L3
{
  var ex:Expr = Deref("L3")

  //memoria para teste
  var gamma: List[(String,Tipo)] = List(("x",Inteiro()),("y",Inteiro()))
  var sigma: List[(String,Int)] = List(("l1",5),("l2",7))

  //lista com variáveis(String) a serem substituídas por expressoes(Expr)
  var lista_subs: List[(String,Expr)] = List()

  var q = 0

  class L3interpreter
  {
    //Declaracao da estrutura dos tipos Endereco e Memoria
    type Endereco = String
    type Memoria = List[(Endereco,Int)]

    /*----------Verificador de Tipos da linguagem L3----------*/

    def typecheck(e:Expr, gamma: List[(String,Tipo)], sigma: Memoria) : Option[Tipo] = e match
    {
      case N (_) => Some(Inteiro())
      case B (_) => Some(Boleano())
      case Raise (e1, e2) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma)) match {

          case (Some(TipoRaise()), Some(tipo)) => Some(tipo)
          case (Some(tipo), _) => Some(tipo)
          case _ => None

        }//match-end_raise
      case Sum (e1, e2) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma)) match {
          case (Some(Inteiro()), Some(Inteiro())) => Some(Inteiro())
          case (Some(TipoRaise()), _) => Some(TipoRaise())
          case ( _ , Some(TipoRaise())) => Some(TipoRaise())
          case _ => None
        }//match-end_soma
      case Prod (e1, e2) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma)) match {
          case (Some(Inteiro()), Some(Inteiro())) => Some(Inteiro())
          case _ => None
        }//match-end_produto
      case Sub (e1, e2) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma)) match {
          case (Some(Inteiro()), Some(Inteiro())) => Some(Inteiro())
          case _ => None
        }//match-end_subtracao
      case Div (e1, e2) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma)) match {
          case (Some(Inteiro()), Some(Inteiro())) => Some(Inteiro())
          case _ => None
        }//match-end_divisao
      case GrEq (e1, e2) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma)) match {
          case (Some(Inteiro()), Some(Inteiro())) => Some(Boleano())
          case _ => None
        }//match-end-maior_igual
      case LessEq (e1, e2) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma)) match {
          case (Some(Inteiro()), Some(Inteiro())) => Some(Boleano())
          case _ => None
        }//match-end-menor_igual
      case Greater (e1, e2) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma)) match {
          case (Some(Inteiro()), Some(Inteiro())) => Some(Boleano())
          case _ => None
        }//match-end-maior
      case Less (e1, e2) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma)) match {
          case (Some(Inteiro()), Some(Inteiro())) => Some(Boleano())
          case _ => None
        }//match-end-menor
      case Equal (e1, e2) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma)) match {
          case (Some(Inteiro()), Some(Inteiro())) => Some(Boleano())
          case _ => None
        }//match-end-igual
      case Dif (e1, e2) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma)) match {
          case (Some(Inteiro()), Some(Inteiro())) => Some(Boleano())
          case _ => None
        }//match-end-diferente
      case If (e1, e2, e3) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma), typecheck(e3,gamma,L3.sigma)) match {
          case (Some(Boleano()), Some(tipo1), Some(tipo2)) =>

            if(tipo1==tipo2){
              Some(tipo1)
            }
            else{
              None
            }
          case _ => None
        }//match-end-if
      case Asg (l:String, e) =>
        (typecheck(e,gamma,L3.sigma)) match
        {
          case (Some(Inteiro())) =>
            if(isIntRefInSigma(l,L3.sigma))
            {
              Some(Unidade())
            }
            else{
              None
            }
          case _ => None
        }//match-end-atribuicao
      case Deref (l:String) =>
        if(isIntRefInSigma(l,L3.sigma))
        {
          Some(Inteiro())
        }
        else
        {
          None
        }
      case Skip() => Some(Unidade())
      case Seq (e1, e2) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma)) match
        {
          case (Some(Unidade()), Some(tlinha)) => Some(tlinha)
          case _ => None
        }//match-end-sequencia
      case W (e1, e2) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma)) match
        {
          case (Some(Boleano()), Some(Unidade())) => Some(Unidade())
          case _ => None
        }//match-end-while
      case Fn (x:String, t: Tipo, e) =>
        (typecheck(e,gamma,L3.sigma)) match
        {
          case Some(tlinha) => Some(Funcao(t,tlinha))
          case _ => None
        }//match-end-funcao
      case App (e1, e2) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma)) match
        {
          case (Some(Funcao(tipo1,tlinha)), Some(tipo2)) =>
            if(tipo1==tipo2)
            {
              Some(tlinha)
            }
            else
            {
              None
            }
          case _ => None
        }//match-end-aplicacao
      case X (x:String) =>
        if(isTipoInGamma(x,gamma))
        {
          tipoInGamma(x,gamma)
        }
        else
        {
          None
        }
      case Let (x:String, t: Tipo, e1, e2) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma)) match
        {
          case (Some(t), Some(tlinha)) => Some(tlinha)
          case _ => None
        }//match-end-declaracao
      case LetRec (s:String, f: Tipo, e1, e2) =>
        (typecheck(e1,gamma,L3.sigma), typecheck(e2,gamma,L3.sigma)) match
        {
          case (Some(Funcao(t_alfa,t2)), Some(t)) =>
            if(isYDefinedInGamma(gamma))
            {
              var ty = tipoOfYInGamma(gamma)
              if((f == Funcao(ty,t2)) && (ty == t_alfa))
              {
                Some(t)
              }
              else
              {
                None
              }
            }
            else
            {
              None
            }
          case _ => None
        }//match-end-declaracao_recursiva

    }// end-verificador(typecheck)


    //Retorna o Tipo de uma String x na memória(gamma)
    def tipoInGamma(x:String, gamma: List[(String,Tipo)]) : Option[Tipo] =
    {

      var achou = false
      var aux = gamma
      var tuple = aux.head
      var tipo = tuple._2
      var i = 0

      while(!achou)
      {
        tuple = aux(i)

        if(tuple._1 == x)
        {
          achou = true
          tipo = tuple._2
        }

        i = i + 1
      }
      Some(tipo)
    }

    //Retorna o Tipo de y na memória(gamma)
    def tipoOfYInGamma(gamma: List[(String,Tipo)]) : Tipo =
    {

      var achou = false
      var aux = gamma
      var tuple = aux.head
      var tipo = tuple._2
      var i = 0

      while(! achou)
      {
        tuple = aux(i)

        if(tuple._1 == "y")
        {
          achou = true
          tipo = tuple._2
        }

        i = i + 1
      }

      tipo
    }


    //Verifica se y está na memória(gamma)
    def isYDefinedInGamma(gamma: List[(String,Tipo)]) : Boolean =
    {
      if(gamma.exists( str => str._1 == "y" ))
      {
    	true
      }
      else
      {
    	false
      }
    }

    //Verifica se uma String x está na memória(gamma)
    def isTipoInGamma(x:String, gamma: List[(String,Tipo)]) : Boolean =
    {
      if(gamma.exists( str => str._1 == x ))
      {
    	true
      }
      else
      {
    	false
      }
    }

    //Verifica se l está na memória(sigma)
    def isIntRefInSigma(l:String, sigma: Memoria) : Boolean = {
      if(L3.sigma.exists( str => str._1 == l))
      {
    	true
      }
      else
      {
    	false
      }
    }

    //Altera o valor de l na memória
    def updateSigma(l:String, number:Int, sigma: Memoria) : Memoria =
    {
      var aux1 = L3.sigma(0)
      var aux2 = List(L3.sigma(0))
      var tuple = L3.sigma(0)
      var i = 0
      var achou = false

      while(i < L3.sigma.length)
      {
        aux1 = L3.sigma(i)

        if(aux1._1 == l)
        {
          tuple = (l,number)
          achou = true
        }
        if(achou)
        {
          if(i!=0) aux2 = aux2 ::: List(tuple)
          else aux2 = List(tuple)

          achou = false
        }
        else
        {
          if(i!=0) aux2 = aux2 ::: List(aux1)
        }

        i = i + 1
      }

      return aux2
    }

    //Retorna o valor de l na memória
    def valor(l:String, sigma: List[(String,Int)]) : Int =
    {
      var achou = false
      var tuple = L3.sigma.head
      var numb = tuple._2
      var i=0

      while(! achou)
      {
        tuple = L3.sigma(i)

        if(tuple._1 == l)
        {
          achou = true
          numb = tuple._2
        }

        i = i + 1
      }

      return numb
    }

    //Insere par ordenado (str,expr) na lista de variaveis a serem substituidas
    def ins_lista_subs(str: String, expr: Expr) : Unit =
    {
      if(L3.lista_subs == Nil)
      {
    	L3.lista_subs = List((str,expr))
      }
      else
      {
        var aux1 = L3.lista_subs(0)
        var aux2 = List(L3.lista_subs(0))
        var tuple = L3.lista_subs(0)
        var i = 0
        var n = 0

        while(i < L3.lista_subs.length)
        {
          aux1 = L3.lista_subs(i)

          if(aux1._1 != str)
          {
            n = 1
          }
          else
          {
            n = 0
          }

          if(n == 1)
          {
            if(i!=0) aux2 = aux2 ::: List(aux1)
            else aux2 = List(tuple)
          }

          i = i + 1
        }

        L3.lista_subs = aux2 ::: List((str,expr))
      }
    }

    //Retorna a expressão referente a variavel de string 'name'
    def search_lista_subs(name: String): Expr =
    {
      if(L3.lista_subs != Nil)
      {
        var aux = L3.lista_subs.head
        var exp = aux._2
        var achou = false
        var i = 0

        while((i < L3.lista_subs.length) && (!achou))
        {
          aux = L3.lista_subs(i)
          if(aux._1 == name)
          {
            exp = aux._2
            achou = true
          }

          i = i + 1
        }

        exp
      }
      else{N(0)}
    }

    //Definicao dos 'tipos dos valores'
    def isvalue(e:Expr) : Boolean = e match
    {
      case N(_) => true
      case B(_) => true
      case Fn(_,_,_) => true
      case Skip() => true
      case _ => false
    }

    //Implementacao das regras do sistema operacional de L3
    def step(e: Expr, sigma: Memoria): Option[(Expr, Memoria)] = e match {
      case N(_) => None
      case B(_) => None
      case Fn(_,_,_) => None
      case Skip() => None
      case Sum (e1, e2) => (e1,e2) match
        {
          case (N(n1),N(n2)) => Some ((N(n1 + n2), L3.sigma))
          case (e1, e2) => if (isvalue(e1))
            {
              step(e2,L3.sigma) match
              {
                case Some((e2lin, sigmalin)) =>
                  Some((Sum(e1,e2lin), sigmalin))
                case None => None
              }
            }
            else
            {
              step(e1, L3.sigma) match
              {
                case Some((e1lin, sigmalin)) =>
                  Some((Sum(e1lin, e2), sigmalin))
                case None => None
              }
            }
        }
      case Prod (e1, e2) => (e1,e2) match
        {
          case (N(n1),N(n2)) => Some ((N(n1 * n2), L3.sigma))
          case (e1, e2) => if (isvalue(e1)) {
              step(e2,L3.sigma) match
              {
                case Some((e2lin, sigmalin)) =>
                  Some((Prod(e1,e2lin), sigmalin))
                case None => None
              }
            }
            else
            {
              step(e1, L3.sigma) match
              {
                case Some((e1lin, sigmalin)) =>
                  Some((Prod(e1lin, e2), sigmalin))
                case None => None
              }
            }
        }
      case Sub (e1, e2) => (e1,e2) match
        {
          case (N(n1),N(n2)) => Some ((N(n1 - n2), L3.sigma))
          case (e1, e2) => if (isvalue(e1))
            {
              step(e2,L3.sigma) match
              {
                case Some((e2lin, sigmalin)) =>
                  Some((Sub(e1,e2lin), sigmalin))
                case None => None
              }
            }
            else
            {
              step(e1, L3.sigma) match
              {
                case Some((e1lin, sigmalin)) =>
                  Some((Sub(e1lin, e2), sigmalin))
                case None => None
              }
            }
        }
      case Div (e1, e2) => (e1,e2) match
        {
          case (N(n1),N(n2)) => Some ((N(n1 / n2), L3.sigma))
          case (e1, e2) => if (isvalue(e1))
            {
              step(e2,L3.sigma) match
              {
                case Some((e2lin, sigmalin)) =>
                  Some((Div(e1,e2lin), sigmalin))
                case None => None
              }
            }
            else
            {
              step(e1, L3.sigma) match
              {
                case Some((e1lin, sigmalin)) =>
                  Some((Div(e1lin, e2), sigmalin))
                case None => None
              }
            }
        }
      case GrEq (e1, e2) => (e1,e2) match
        {
          case (N(n1),N(n2)) => Some((B(n1 >= n2),L3.sigma))
          case (e1, e2) => if(isvalue(e1))
            {
              step(e2,L3.sigma) match
              {
                case Some((e2lin, sigmalin)) =>
                  Some((GrEq(e1,e2lin), sigmalin))
                case None => None
              }
            }
            else
            {
              step(e1, L3.sigma) match
              {
                case Some((e1lin, sigmalin)) =>
                  Some((GrEq(e1lin, e2), sigmalin))
                case None => None
              }
            }
        }
      case LessEq (e1, e2) => (e1,e2) match
        {
          case (N(n1),N(n2)) => Some((B(n1 <= n2),L3.sigma))
          case (e1, e2) => if(isvalue(e1))
            {
              step(e2,L3.sigma) match {
                case Some((e2lin, sigmalin)) =>
                  Some((LessEq(e1,e2lin), sigmalin))
                case None => None
              }
            }
            else
            {
              step(e1, L3.sigma) match
              {
                case Some((e1lin, sigmalin)) =>
                  Some((LessEq(e1lin, e2), sigmalin))
                case None => None
              }
            }
        }
      case Greater (e1, e2) => (e1,e2) match
        {
          case (N(n1),N(n2)) => Some((B(n1 > n2),L3.sigma))
          case (e1, e2) => if(isvalue(e1))
            {
              step(e2,L3.sigma) match
              {
                case Some((e2lin, sigmalin)) =>
                  Some((Greater(e1,e2lin), sigmalin))
                case None => None
              }
            }
            else {
              step(e1, L3.sigma) match
              {
                case Some((e1lin, sigmalin)) =>
                  Some((Greater(e1lin, e2), sigmalin))
                case None => None
              }
            }
        }
      case Less (e1, e2) => (e1,e2) match
        {
          case (N(n1),N(n2)) => Some((B(n1 < n2),L3.sigma))
          case (e1, e2) => if(isvalue(e1))
            {
              step(e2,L3.sigma) match
              {
                case Some((e2lin, sigmalin)) =>
                  Some((Less(e1,e2lin), sigmalin))
                case None => None
              }
            }
            else
            {
              step(e1, L3.sigma) match
              {
                case Some((e1lin, sigmalin)) =>
                  Some((Less(e1lin, e2), sigmalin))
                case None => None
              }
            }
        }
      case Equal (e1, e2) => (e1,e2) match
        {
          case (N(n1),N(n2)) => Some((B(n1 == n2),L3.sigma))
          case (e1, e2) => if(isvalue(e1)) {
              step(e2,L3.sigma) match {
                case Some((e2lin, sigmalin)) =>
                  Some((Equal(e1,e2lin), sigmalin))
                case None => None
              }
            }
            else {
              step(e1, L3.sigma) match {
                case Some((e1lin, sigmalin)) =>
                  Some((Equal(e1lin, e2), sigmalin))
                case None => None
              }
            }
        }
      case Dif (e1, e2) => (e1,e2) match
        {
          case (N(n1),N(n2)) => Some((B(n1 != n2),L3.sigma))
          case (e1, e2) => if(isvalue(e1))
            {
              step(e2,L3.sigma) match
              {
                case Some((e2lin, sigmalin)) => Some((Dif(e1,e2lin), sigmalin))
                case None => None
              }
            }
            else
            {
              step(e1, L3.sigma) match {
                case Some((e1lin, sigmalin)) =>
                  Some((Dif(e1lin, e2), sigmalin))
                case None => None
              }
            }
        }
      case If(B(true), e2, e3) => Some((e2, L3.sigma))
      case If(B(false), e2, e3) => Some((e3, L3.sigma))
      case If (e1, e2, e3) => (e1, e2, e3) match
        {
          case (e1, e2, e3) =>
            if(isvalue(e1))
            {
              None
            }
            else
            {
              step(e1,L3.sigma) match
              {
                case Some((e1lin, sigmalin)) => Some((If(e1lin,e2,e3), sigmalin))
                case None => None
              }
            }
        }
      case Seq(Skip(),e2) => Some((e2,L3.sigma))
      case Seq(e1,e2) => (e1,e2) match
        {
          case (e1,e2) =>
            if(isvalue(e1)) {
              None
            }
            else{
              step(e1,L3.sigma) match{
                case Some((e1lin,sigmalin)) => Some((Seq(e1lin,e2),sigmalin))
                case None => None
              }
            }
        }
      case Asg(l:String,e) => (l,e) match
        {
          case (l,N(n)) =>
            if(isIntRefInSigma(l,L3.sigma)){
              L3.sigma = updateSigma(l,n,L3.sigma)
              Some((Skip(),L3.sigma))
            }
            else{
              None
            }

          case (l,e) =>
            if(isvalue(e)){
              None
            }
            else{
              step(e,L3.sigma) match{
                case Some((elin,sigmalin)) =>
                  Some((Asg(l,elin),sigmalin))
                case None => None
              }
            }
        }
      case Deref(l:String) =>
        if(isIntRefInSigma(l,L3.sigma))
        {
          q = valor(l,L3.sigma)
          Some((N(q),L3.sigma))
        }
        else
        {
          None
        }
      case W(e1,e2) => Some((If(e1,Seq(e2,W(e1,e2)),Skip()),L3.sigma))
      case X(s) => Some((search_lista_subs(s),L3.sigma))
      case App(e1,e2) => (e1,e2) match
        {
          case (e1,e2) =>
            if(isvalue(e1)){
              if(isvalue(e2)){
                (e1,e2) match{
                  case (Fn(x,r,e),valor) =>
                    ins_lista_subs(x,valor)
                    Some((e,L3.sigma))
                  case _ => None
                }
              }
              else{
                step(e2,L3.sigma) match{
                  case Some((e2lin,sigmalin)) =>
                    Some((App(e1,e2lin),sigmalin))
                  case None => None
                }
              }
            }
            else{
              step(e1,L3.sigma) match{
                case Some((e1lin,sigmalin)) =>
                  Some((App(e1lin,e2),sigmalin))
                case None => None
              }
            }
        }
      case Let(x:String, tipolet, e1, e2) =>
        if(isvalue(e1))
        {
          ins_lista_subs(x,e1)
          Some((e2,L3.sigma))
        }
        else
        {
          step(e1,L3.sigma) match{
            case Some((e1lin,sigmalin)) =>
              Some((Let(x,tipolet,e1lin,e2),sigmalin))
            case None => None
          }
        }
      case LetRec (f:String, tipoletrec, e1, e2) => (f,tipoletrec,e1,e2) match
        {
          case (f,tipoletrec,Fn(f1,f2,e),e2) =>
            ins_lista_subs(f,Fn("y",tipoOfYInGamma(gamma),LetRec(f,tipoletrec,Fn(f1,f2,e),e)))
            Some((e2,L3.sigma))
          case _ => None
        }

    }//match-end-regras_sistema_operacional

    /*----------------Avaliador da linguagem L3---------------*/

    //Avaliacao dos passos de uma expressao com uma memoria
    def eval(e: Expr, sigma:Memoria): Option[(Expr, Memoria)] =
      step(e,sigma) match {
        case None => Some(e,sigma)
        case Some((elin, sigmalin)) => eval(elin, sigmalin)
      }//end-eval

  }//end-class-L3interpreter


  def main(args : Array[String]) : Unit = {
    /*-------------------------------------------------------*/

    //testes

    val ex3:Expr = Sum(Raise(B(false), B(true)), N(5))

    // ex3 = Boleano( (5+10) >= (10+5) )
    // val ex3:Expr = GrEq( Sum(N(5),N(10)) , Sum(N(10),N(5)) )
    //
    // ex4 = if(ex3) then ((5+10)+(10+0)) else ((588+10)+(10+100))
    // val ex4:Expr = If( GrEq( Sum(N(5),N(10)) , Sum(N(10),N(5)) ) , Sum( Sum(N(5),N(10)) , Sum(N(10),N(0)) ) , Sum( Sum(N(588),N(10)) , Sum(N(10),N(100)) ) )

    // ex = ( L3:=100 ; l1:=100 )
    // ex = Seq( Asg("L3",N(100)) , Asg("l1",N(100)) )

    // ex = while(!L3 >= 5) L3:=2
    // ex = W( GrEq(Deref("L3"),N(5)) , Asg("L3",N(2)) )

    // ex = Deref("L3")
    // ex = Seq(Asg("L3",N(100)),Deref("L3"))
    // ex = Seq( Asg("L3",N(100)) , Asg("l1",N(100)) )
    // ex = Asg("L3",N(100))

    //fatorial de 5 com letrec
    //var ex3:Expr = If(GrEq(N(0),X("y")),N(1),Prod(X("y"),App(X("fat"),Sub(X("y"),N(1)))))

    //ex = LetRec("fat",Funcao(Inteiro(),Inteiro()),Fn("y",Inteiro(),ex3),App(X("fat"),N(5)))

    // dobro de um valor passado como parâmetro(aplicacao) no let
    // ex = Let("x",Funcao(Inteiro(),Inteiro()),Fn("y",Inteiro(),Prod(X("y"),N(2))),App(X("x"),N(20)))

    // ex = (x + x)5
    // val ex2:Expr = Sum(X("x"),X("x"))
    // ex = App(Fn("x",Inteiro(),ex2),N(5))



    //sigma = List(("l1",5),("l2",7))
    //gamma = List(("x",Inteiro()),("y",Inteiro()),("fat",Funcao(Inteiro(),Inteiro())))
    // gamma = List(("x",Funcao(Inteiro(),Inteiro())),("y",Inteiro()))

    println("Memoria inicial: " + sigma)

    //chamadas ao interpretador
    var interpretador = new L3interpreter()

    var tipo = interpretador.typecheck(ex3,gamma,sigma)

    println("-------------------------------------------------------------")
    println("Expressao: " + ex3)
    println("-------------------------------------------------------------")
    println("Tipo: " + tipo)










    /*______________________TESTE________________________
     var res = interpretador.eval(ex,sigma)
     res match{
     case Some((exp_final,sigma_final)) =>
     println("Resultado da avaliacao: " + exp_final)
     println("Memoria da final: " + sigma_final)
     case None => None
     }

     -------------------------------------------------------*/
  }
}